package top.wangjiu.java.jvm;

import top.wangjiu.java.jvm.accessflag.ClassAccessFlag;
import top.wangjiu.java.jvm.attribute.*;
import top.wangjiu.java.jvm.attribute.info.ExeceptionInfo;
import top.wangjiu.java.jvm.attribute.info.InnerClassesInfo;
import top.wangjiu.java.jvm.attribute.info.LineNumberInfo;
import top.wangjiu.java.jvm.attribute.info.LocalVariableInfo;
import top.wangjiu.java.jvm.constantinfo.*;
import top.wangjiu.java.jvm.constantinfo.literal.*;
import top.wangjiu.java.jvm.constantinfo.refrence.*;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

/**
 * @ClassName ParseClass
 * @Description TODO
 * @Author wangjiu
 * @Date 2022/1/14 10:56 上午
 */
public class ParseClass {

    public static void main(String[] args) {
        if(args.length==0){
            System.out.println("please provide class path like /tmp/Object.class");
            return;
        }
        String fileName = args[0];
        try(InputStream input = new FileInputStream(fileName)) {
            //检查魔数(u4 magic)
            checkMagic(input);
            //检查版本
            checkVersion(input);
            //解析常量池
            ConstantPoolInfo[] poolInfos = parseConstantPool(input);
            printConstantPoolInfo(poolInfos);
            //解析权限
            short accessFlags = getAccessFlags(input);
            printClassAccessFlags(accessFlags);
            //解析当前类名
            short thisClassCPIndex = readShort(input);
            printClassName(thisClassCPIndex,poolInfos);
            //解析父类名
            short superClassCPIndex = readShort(input);
            printSuperClassName(superClassCPIndex,poolInfos);
            //解析接口
            InterfaceInfo[] interfaceInfos = parseInterfaceInfo(input);
            printInterfaceInfo(interfaceInfos,poolInfos);
            //解析字段表
            FieldInfo[] fieldInfos = parseFieldInfo(input,poolInfos);
            printFieldInfo(fieldInfos,poolInfos);
            //解析方法表
            MethodInfo[] methodInfos = parseMethodInfo(input,poolInfos);
            printMethodInfo(methodInfos,poolInfos);
            //解析属性表
            AttributeInfo[] attributes = parseAttributeInfos(input,poolInfos);
            printAttributeInfo(attributes,poolInfos);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void printFieldInfo(FieldInfo[] fieldInfos,ConstantPoolInfo[] poolInfos){
        System.out.println("FieldInfo count:"+fieldInfos.length);
        for (int i = 0; i <fieldInfos.length; i++) {
            String str = fieldInfos[i].toString(poolInfos);
            if(str!=null){
                System.out.println(str);
            }
        }
    }

    private static void printMethodInfo(MethodInfo[] methodInfos,ConstantPoolInfo[] poolInfos){
        System.out.println("MethodInfo count:"+methodInfos.length);
        for (int i = 0; i <methodInfos.length; i++) {
            String str = methodInfos[i].toString(poolInfos);
            if(str!=null){
                System.out.println(str);
            }
        }
    }

    private static void printAttributeInfo(AttributeInfo[] attributes,ConstantPoolInfo[] poolInfos){
        System.out.println("Attributes count:"+attributes.length);
        for (int i = 0; i <attributes.length; i++) {
            String str = attributes[i].toString(poolInfos);
            if(str!=null){
                System.out.println("\t"+str);
            }
        }
    }

    private static MethodInfo[] parseMethodInfo(InputStream input, ConstantPoolInfo[] poolInfos) {
        short count = readShort(input);
        MethodInfo[] infos = new MethodInfo[count];
        for (short i = 0; i <count; i++) {
            short accessFlags = readShort(input);
            short nameIndex = readShort(input);
            short descriptorIndex = readShort(input);
            AttributeInfo[] attributes = parseAttributeInfos(input,poolInfos);
            infos[i] = new MethodInfo(accessFlags,nameIndex,descriptorIndex,attributes);
        }
        return infos;
    }

    private static AttributeInfo[] parseAttributeInfos(InputStream input, ConstantPoolInfo[] poolInfos) {
        short count = readShort(input);
        AttributeInfo[] attributes = new AttributeInfo[count];
        for (int i = 0; i < count; i++) {
            attributes[i] = parseAttributeInfo(input,poolInfos);
        }
        return attributes;
    }

    private static FieldInfo[] parseFieldInfo(InputStream input,ConstantPoolInfo[] poolInfos) {
        short count = readShort(input);
        FieldInfo[] infos = new FieldInfo[count];
        for (short i = 0; i <count; i++) {
            infos[i] = parseFieldInfo(input,poolInfos,i);
        }
        return infos;
    }

    private static FieldInfo parseFieldInfo(InputStream input,ConstantPoolInfo[] poolInfos,short index) {
        short accessFlags = readShort(input);
        short nameIndex = readShort(input);
        short  descriptorIndex = readShort(input);
        short  attributesCount = readShort(input);
        AttributeInfo[] attributes = new AttributeInfo[attributesCount];
        for (short i = 0; i <attributesCount; i++) {
            attributes[i] = parseAttributeInfo(input,poolInfos);
        }
        return new FieldInfo(index,accessFlags,nameIndex,descriptorIndex,attributes);
    }

    private static AttributeInfo parseAttributeInfo(InputStream input,ConstantPoolInfo[] poolInfos) {
        short attributeNameIndex = readShort(input);   //属性名索引
        int attributeLength = readInt(input);          //属性长度
        String attrName = poolInfos[attributeNameIndex].toString(poolInfos);
        switch (attrName){
            case CodeAttr.NAME:return buildCodeAttr(input,attributeNameIndex,attributeLength,poolInfos);
            case ConstantValueAttr.NAME:return new ConstantValueAttr(attributeNameIndex,getBytes(input,attributeLength));
            case DeprecatedAttr.NAME:return new DeprecatedAttr(attributeNameIndex,attributeLength);
            case ExceptionsAttr.NAME:return buildExeceptionsAttr(input,attributeNameIndex,attributeLength);
            case InnerClassesAttr.NAME:return buildInnerClassesAttr(input,attributeNameIndex,attributeLength);
            case LineNumberTableAttr.NAME:return buildLineNumberTableAttr(input,attributeNameIndex,attributeLength);
            case LocalVariableTableAttr.NAME:return buildLocalVariableTableAttr(input,attributeNameIndex,attributeLength);
            case SignatureAttr.NAME:return new SignatureAttr(attributeNameIndex,getBytes(input,attributeLength));
            case SourceFileAttr.NAME:return new SourceFileAttr(attributeNameIndex,getBytes(input,attributeLength));
            case SyntheticAttr.NAME:return new SyntheticAttr(attributeNameIndex,attributeLength);
            //注解属性还未实现

            default:
                return new UnrecognizedAttr(attributeNameIndex,attrName,getBytes(input,attributeLength));
        }
    }

    private static LineNumberTableAttr buildLineNumberTableAttr(InputStream input,short attributeNameIndex,int attributeLength){

        short lineNumberLength = readShort(input);
        LineNumberInfo[] lineNumberTable = new LineNumberInfo[lineNumberLength];

        for (int i = 0; i < lineNumberLength; i++) {
            short startPc = readShort(input);
            short lineNumber = readShort(input);
            lineNumberTable[i] = new LineNumberInfo(startPc,lineNumber);
        }

        return new LineNumberTableAttr(attributeNameIndex,attributeLength,lineNumberTable);
    }

    private static LocalVariableTableAttr buildLocalVariableTableAttr(InputStream input,short attributeNameIndex,int attributeLength){

        short localVariableTableLength = readShort(input);
        LocalVariableInfo[] localVariableTable = new LocalVariableInfo[localVariableTableLength];

        for (int i = 0; i < localVariableTableLength; i++) {
            short startPc = readShort(input);
            short length = readShort(input);
            short nameIndex = readShort(input);
            short descriptorIndex = readShort(input);
            short index = readShort(input);
            localVariableTable[i] = new LocalVariableInfo(startPc,length,nameIndex,descriptorIndex,index);
        }

        return new LocalVariableTableAttr(attributeNameIndex,attributeLength,localVariableTable);
    }

    private static InnerClassesAttr buildInnerClassesAttr(InputStream input,short attributeNameIndex,int attributeLength){

        short numberOfClasses = readShort(input);
        InnerClassesInfo[] innerClasses = new InnerClassesInfo[numberOfClasses];

        for (int i = 0; i < numberOfClasses; i++) {
            short innerClassInfoIndex = readShort(input);
            short outerClassInfoIndex = readShort(input);
            short innerNameIndex = readShort(input);
            short innerClassAccessFlags = readShort(input);
            innerClasses[i] = new InnerClassesInfo(innerClassInfoIndex,outerClassInfoIndex,innerNameIndex,innerClassAccessFlags);
        }

        return new InnerClassesAttr(attributeNameIndex,attributeLength,innerClasses);
    }

    private static ExceptionsAttr buildExeceptionsAttr(InputStream input, short attributeNameIndex, int attributeLength){

        short numberOfExceptions = readShort(input);
        short[] exceptionIndexTable = new short[numberOfExceptions];
        for (int i = 0; i < numberOfExceptions; i++) {
            exceptionIndexTable[i] = readShort(input);
        }
        return new ExceptionsAttr(attributeNameIndex,attributeLength,exceptionIndexTable);
    }

    private static CodeAttr buildCodeAttr(InputStream input,short attributeNameIndex,int attributeLength,ConstantPoolInfo[] poolInfos){

        short maxStack = readShort(input);   //操作数栈的最大深度值，jvm运行时根据该值分配栈帧
        short maxLocals = readShort(input);  //局部变量表最大存储空间，单位是slot
        int  codeLength = readInt(input); // 字节码指令的个数,Java虚拟机规范中限制一个方法不能超过65535条字节码指令
        byte[] codes = getBytes(input,codeLength); // 具体的字节码指令
        ExeceptionInfo[] execeptionTable = buildExeceptionInfo(input);
        short attributesCount = readShort(input);     //属性的个数
        AttributeInfo[] attributes = new AttributeInfo[attributesCount];//具体的属性内容
        for (short i = 0; i <attributesCount; i++) {
            attributes[i] = parseAttributeInfo(input,poolInfos);
        }
        return new CodeAttr(attributeNameIndex,attributeLength,maxStack,maxLocals,codes,execeptionTable,attributes);
    }

    private static ExeceptionInfo[] buildExeceptionInfo(InputStream input){
        short exceptionTableLength = readShort(input); //异常的个数
        ExeceptionInfo[] execeptionTable = new ExeceptionInfo[exceptionTableLength];//具体的异常内容
        for (int i = 0; i < exceptionTableLength; i++) {
            short startPc = readShort(input);
            short endPc = readShort(input);
            short handlerPc = readShort(input); //当字节码在[start_pc, end_pc)区间出现catch_type或子类，则转到handler_pc行继续处理。
            short catchType = readShort(input);
            execeptionTable[i] = new ExeceptionInfo(startPc,endPc,handlerPc,catchType);
        }
        return execeptionTable;
    }

    private static void printInterfaceInfo(InterfaceInfo[] interfaceInfos,ConstantPoolInfo[] poolInfos) {
        System.out.println("interface count:"+interfaceInfos.length);
        for (int i = 0; i < interfaceInfos.length; i++) {
            System.out.println("\t"+interfaceInfos[i].toString(poolInfos));
        }
    }

    private static InterfaceInfo[] parseInterfaceInfo(InputStream input) {
        short count = readShort(input);
        InterfaceInfo[] infos = new InterfaceInfo[count];
        for (short i = 0;i<count;i++){
            infos[i] = new InterfaceInfo(i,readShort(input));
        }
        return infos;
    }

    private static void printClassName(short cPIndex, ConstantPoolInfo[] poolInfos) {
        System.out.println("class: "+poolInfos[cPIndex].toString(poolInfos).replace("/","."));
    }

    private static void printSuperClassName(short cPIndex, ConstantPoolInfo[] poolInfos) {
        System.out.println("super class: "+poolInfos[cPIndex].toString(poolInfos).replace("/","."));
    }

    private static void printClassAccessFlags(short accessFlags) {
        StringBuilder sb = new StringBuilder();
        for (ClassAccessFlag accessFlag : ClassAccessFlag.parseAccessFlags(accessFlags)) {
            sb.append(" "+accessFlag.name());
        }
        System.out.println("access flags:"+sb);
    }

    private static short getAccessFlags(InputStream input) {
        return readShort(input);
    }

    private static short readShort(InputStream input) {
        byte[] bytes = getBytes(input, 2);
        return (short) (bytes[0]<<8 | bytes[1]);
    }

    private static int readInt(InputStream input) {
        byte[] bytes = getBytes(input, 4);
        return (bytes[0]<<24 |bytes[1]<<16 |bytes[2]<<8 | bytes[3]);
    }


    private static void printConstantPoolInfo(ConstantPoolInfo[] poolInfos){
        System.out.println("constant_pool_count:"+poolInfos.length);
        for (ConstantPoolInfo poolInfo : poolInfos) {
            String str = poolInfo.toFullString(poolInfos);
            if(str!=null){
                System.out.println("\t"+str);
            }
        }
    }

    /**
     * u2 constant_pool_count
     * cp_info 常量池项
     * @param input
     * @throws IOException
     */
    private static ConstantPoolInfo[] parseConstantPool(InputStream input) {
        byte[] bytes = getBytes(input, 2);
        short count = (short)(bytes[0] << 8 | bytes[1]);

        ConstantPoolInfo[] poolInfos = new ConstantPoolInfo[count];
        poolInfos[0] = new InvalidCPInfo((short) 0);
        for (short i = 1; i < count; i++) {
            ConstantPoolInfo info = parseConstantPoolInfo(input, i);
            poolInfos[i] = info;
            if(info instanceof LongCPInfo || info instanceof DoubleCPInfo){
                poolInfos[++i] = new InvalidCPInfo(i);
            }
        }
        return poolInfos;
    }
    /**
     * cp_info {
     *     u1 tag;
     *     u1 info[];
     * }
     * @param input
     * @throws IOException
     */
    private static ConstantPoolInfo parseConstantPoolInfo(InputStream input,short index) {
        short cpInfoTag = getBytes(input, 1)[0];
        switch (cpInfoTag){
            case Utf8CPInfo.TAG:
                short length = readShort(input);
                return new Utf8CPInfo(getBytes(input,length),length,index);
            case ClassCPInfo.TAG:return new ClassCPInfo(getBytes(input, ClassCPInfo.INFO_LENGTH),index);
            case FieldCPInfo.TAG:return new FieldCPInfo(getBytes(input, FieldCPInfo.INFO_LENGTH),index);
            case MethodrefCPInfo.TAG:return new MethodrefCPInfo(getBytes(input, MethodrefCPInfo.INFO_LENGTH),index);
            case InterfaceMethodrefCPInfo.TAG:return new InterfaceMethodrefCPInfo(getBytes(input, InterfaceMethodrefCPInfo.INFO_LENGTH),index);
            case StringCPInfo.TAG:return new StringCPInfo(getBytes(input, StringCPInfo.INFO_LENGTH),index);
            case IntegerCPInfo.TAG:return new IntegerCPInfo(getBytes(input, IntegerCPInfo.INFO_LENGTH),index);
            case FloatCPInfo.TAG:return new FloatCPInfo(getBytes(input, FloatCPInfo.INFO_LENGTH),index);
            case LongCPInfo.TAG:return new LongCPInfo(getBytes(input, LongCPInfo.INFO_LENGTH),index);
            case DoubleCPInfo.TAG:return new DoubleCPInfo(getBytes(input, DoubleCPInfo.INFO_LENGTH),index);
            case NameAndTypeCPInfo.TAG:return new NameAndTypeCPInfo(getBytes(input, NameAndTypeCPInfo.INFO_LENGTH),index);
            case MethodHandleCPInfo.TAG:return new MethodHandleCPInfo(getBytes(input, MethodHandleCPInfo.INFO_LENGTH),index);
            case MethodTypeCPInfo.TAG:return new MethodTypeCPInfo(getBytes(input, MethodTypeCPInfo.INFO_LENGTH),index);
            case InvokeDynamicCPInfo.TAG:return new InvokeDynamicCPInfo(getBytes(input, InvokeDynamicCPInfo.INFO_LENGTH),index);

            default:
                throw new RuntimeException("unrecognized constant pool info tag:"+cpInfoTag);
        }
    }

    /**
     * u2 minor_version：副版本号
     * u2 major_version：主版本号
     * @param input
     * @throws IOException
     */
    static void checkVersion(InputStream input) throws IOException{
        byte[] version = getBytes(input,4);
        short minorVersion = (short)(version[0] << 8 | version[1]);
        short majorVersion = (short)(version[2] << 8 | version[3]);
        System.out.println("minorVersion:"+minorVersion);
        System.out.println("majorVersion:"+majorVersion);
    }

    static void checkMagic(InputStream input) throws IOException {
        byte[] magic = getBytes(input,4);
        String magicHexString = getHexString(magic);
        if(!magicHexString.equals("cafebabe")){
            throw new RuntimeException("this class file is illegal:"+magicHexString);
        }
        System.out.println("magic:"+magicHexString);
    }


    static String getHexString( byte[] b) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < b.length; i++) {
            String hex = Integer.toHexString(b[i] & 0xFF);
            if (hex.length() == 1) {
                hex = '0' + hex;
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    static byte[] getBytes(InputStream input,int length){
        byte[] bytes = new byte[length];
        try {
            input.read(bytes);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bytes;
    }
}
